// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * This comment was taken from the original sony-laptop code
 *
 * ACPI Sony Notebook Control Driver (SNC and SPIC)
 *
 * Copyright (C) 2004-2005 Stelian Pop <stelian@popies.net>
 * Copyright (C) 2007-2009 Mattia Dongili <malattia@linux.it>
 *
 * Parts of this driver inspired from asus_acpi.c and ibm_acpi.c
 * which are copyrighted by their respective authors.
 *
 * The SNY6001 driver part is based on the sonypi driver which includes
 * material from:
 *
 * Copyright (C) 2001-2005 Stelian Pop <stelian@popies.net>
 *
 * Copyright (C) 2005 Narayanan R S <nars@kadamba.org>
 *
 * Copyright (C) 2001-2002 Alcôve <www.alcove.com>
 *
 * Copyright (C) 2001 Michael Ashley <m.ashley@unsw.edu.au>
 *
 * Copyright (C) 2001 Junichi Morita <jun1m@mars.dti.ne.jp>
 *
 * Copyright (C) 2000 Takaya Kinjo <t-kinjo@tc4.so-net.ne.jp>
 *
 * Copyright (C) 2000 Andrew Tridgell <tridge@valinux.com>
 *
 * Earlier work by Werner Almesberger, Paul `Rusty' Russell and Paul Mackerras.
 */


#include <linux/acpi.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/delay.h>
#include <linux/dmi.h>
#include <linux/input.h>

#include "sony-pic.h"


/***********************************************************
*                                                          *
*   This module based on sony-laptop from kernel sources   *
*       Sony Programmable IO Control - SPIC (SNY6001)      *
*                                                          *
***********************************************************/


int  sonypi_compat_init( void );
void sonypi_compat_exit(void);
void sonypi_compat_report_event( u8 event );



int compat;// = 0 
module_param(compat, int, 0444);
MODULE_PARM_DESC( compat, "set this if you want to enable backward compatibility mode" );


static int camera;		/* = 0 */
module_param(camera, int, 0444);
MODULE_PARM_DESC(camera,
		 "set this to 1 to enable Motion Eye camera controls "
		 "(only use it if you have a C1VE or C1VN model)");


/********************
*    INPUT DEVICE   *
********************/

static void vgnux_input_sendkey( int key ){
	if( key ){
		input_report_key( vgnux_input_device, key, 1 );
		input_sync( vgnux_input_device );
		
		input_report_key( vgnux_input_device, key, 0 );
		input_sync( vgnux_input_device );
	}
}

static int vgnux_register_input( void ){
	
	int err = 0;
	int i;
	
	vgnux_input_device = input_allocate_device();
	if( !vgnux_input_device )return -ENOMEM;
	
	vgnux_input_device->name       = KBUILD_MODNAME;
	vgnux_input_device->id.bustype = BUS_ISA;
	vgnux_input_device->id.vendor  = PCI_VENDOR_ID_SONY;
	
	vgnux_input_device->keycodesize = sizeof( vgnux_input_keycode_map[0] );
	vgnux_input_device->keycodemax = ARRAY_SIZE( vgnux_input_keycode_map );
	vgnux_input_device->keycode = &vgnux_input_keycode_map;
	
	for( i = 0; vgnux_input_keycode_map[i] != KEY_RESERVED; i++ ){
		input_set_capability( vgnux_input_device, EV_KEY, vgnux_input_keycode_map[i] );
	}
	
	err = input_register_device( vgnux_input_device );
	if( err ) input_free_device( vgnux_input_device );
	
	return err;
}

static void vgnux_unregister_input( void ){
	input_unregister_device( vgnux_input_device );
}


/************************
*    Platform Device    *
************************/

static int sony_pf_add( void ){
	int ret = 0;
	
	ret = platform_driver_register( &sony_pf_driver );
	if (ret) goto out;

	sony_pf_device = platform_device_alloc( KBUILD_MODNAME, -1 );
	if (!sony_pf_device) {
		ret = -ENOMEM;
		goto out_platform_registered;
	}

	ret = platform_device_add(sony_pf_device);
	if (ret) goto out_platform_alloced;

	return 0;

out_platform_alloced:
	platform_device_put(sony_pf_device);
	sony_pf_device = NULL;
out_platform_registered:
	platform_driver_unregister(&sony_pf_driver);
out:
	return ret;
}

static void sony_pf_remove(void){
	platform_device_unregister(sony_pf_device);
	platform_driver_unregister(&sony_pf_driver);
}

/*****************************
*    low level spic calls    *
******************************/

#define ITERATIONS_LONG		10000
#define ITERATIONS_SHORT	10
#define wait_on_command(command, iterations) { \
	unsigned int n = iterations;               \
	while( --n && (command) )udelay(1);        \
	if (!n) printk( KERN_WARNING "[%s] command failed at %s : %s (line %d)\n", \
				KBUILD_MODNAME, __FILE__, __func__, __LINE__);                 \
}

u8 sony_pic_call1(u8 dev){
	u8 v1, v2;
	wait_on_command( inb_p( spic_dev.cur_ioport->io1.minimum + 4 ) & 2, ITERATIONS_LONG );
	outb( dev, spic_dev.cur_ioport->io1.minimum + 4 );
	v1 = inb_p( spic_dev.cur_ioport->io1.minimum + 4 );
	v2 = inb_p( spic_dev.cur_ioport->io1.minimum );
	//printk( KERN_NOTICE "[%s] %s(0x%.2x): 0x%.4x\n", KBUILD_MODNAME, __FUNCTION__, dev, (v2 << 8) | v1 );
	return v2;
}

u8 sony_pic_call2(u8 dev, u8 fn){
	u8 v1;
	wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG );
	outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
	wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG );
	outb(fn, spic_dev.cur_ioport->io1.minimum);
	v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
	//printk( KERN_NOTICE "[%s] %s(0x%.2x - 0x%.2x): 0x%.4x\n", KBUILD_MODNAME, __FUNCTION__, dev, fn, v1 );
	return v1;
}

static u8 sony_pic_call3(u8 dev, u8 fn, u8 v){
	u8 v1;
	wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
	outb(dev, spic_dev.cur_ioport->io1.minimum + 4);
	wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
	outb(fn, spic_dev.cur_ioport->io1.minimum);
	wait_on_command(inb_p(spic_dev.cur_ioport->io1.minimum + 4) & 2, ITERATIONS_LONG);
	outb(v, spic_dev.cur_ioport->io1.minimum);
	v1 = inb_p(spic_dev.cur_ioport->io1.minimum);
	//printk( KERN_NOTICE "[%s] %s(0x%.2x - 0x%.2x - 0x%.2x): 0x%.4x\n", KBUILD_MODNAME, __FUNCTION__, dev, fn, v, v1 );
	return v1;
}

/*******************
*      CAMERA      *
*******************/
//only for export
static int __sony_pic_camera_ready(void){
	u8 v;
	v = sony_pic_call2(0x8f, SONYPI_CAMERA_STATUS);
	return (v != 0xff && (v & SONYPI_CAMERA_STATUS_READY));
}

static int __sony_pic_camera_off(void){
	if (!camera) {
		pr_warn("camera control not enabled\n");
		return -ENODEV;
	}

	wait_on_command(sony_pic_call3(0x90, SONYPI_CAMERA_PICTURE,
				SONYPI_CAMERA_MUTE_MASK),
			ITERATIONS_SHORT);

	if (spic_dev.camera_power) {
		sony_pic_call2(0x91, 0);
		spic_dev.camera_power = 0;
	}
	return 0;
}

static int __sony_pic_camera_on(void){
	int i, j, x;

	if (!camera) {
		pr_warn("camera control not enabled\n");
		return -ENODEV;
	}

	if (spic_dev.camera_power)return 0;

	for (j = 5; j > 0; j--) {

		for (x = 0; x < 100 && sony_pic_call2(0x91, 0x1); x++)
			msleep(10);
		sony_pic_call1(0x93);

		for (i = 400; i > 0; i--) {
			if( __sony_pic_camera_ready() ) break;
			msleep(10);
		}
		if( i ) break;
	}

	if (j == 0) {
		pr_warn("failed to power on camera\n");
		return -ENODEV;
	}

	wait_on_command( sony_pic_call3(0x90, SONYPI_CAMERA_CONTROL, 0x5a), ITERATIONS_SHORT );

	spic_dev.camera_power = 1;
	return 0;
}

/* External camera command (exported to the motion eye v4l driver) */
int sony_pic_camera_command(int command, u8 value){
	if (!camera) return -EIO;

	mutex_lock(&spic_dev.lock);

	switch (command) {
		case SONY_PIC_COMMAND_SETCAMERA:
			if( value ) __sony_pic_camera_on();
			else __sony_pic_camera_off();
			break;
		case SONY_PIC_COMMAND_SETCAMERABRIGHTNESS:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_BRIGHTNESS, value ), ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERACONTRAST:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_CONTRAST, value ),   ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERAHUE:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_HUE, value ),        ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERACOLOR:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_COLOR, value ),      ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERASHARPNESS:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_SHARPNESS, value ),  ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERAPICTURE:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_PICTURE, value ),    ITERATIONS_SHORT );
			break;
		case SONY_PIC_COMMAND_SETCAMERAAGC:
			wait_on_command( sony_pic_call3( 0x90, SONYPI_CAMERA_AGC, value ),        ITERATIONS_SHORT );
			break;
		default:
			pr_err("sony_pic_camera_command invalid: %d\n", command);
			break;
	}
	mutex_unlock(&spic_dev.lock);
	return 0;
}
EXPORT_SYMBOL(sony_pic_camera_command);


/***************************************************************
*  gprs/edge modem (SZ460N and SZ210P), thanks to Joshua Wise  *
***************************************************************/
static void __sony_pic_set_wwanpower( u8 state ){
	state = !!state;
	if ( spic_dev.wwan_power == state ) return;
	sony_pic_call2( 0xB0, state );
	sony_pic_call1( 0x82 );
	spic_dev.wwan_power = state;
}

static ssize_t sony_pic_wwanpower_store( struct device *dev, struct device_attribute *attr, const char *buffer, size_t count ){
	unsigned long value;
	if (count > 31) return -EINVAL;
	if (kstrtoul(buffer, 10, &value)) return -EINVAL;
	
	mutex_lock(&spic_dev.lock);
	__sony_pic_set_wwanpower(value);
	mutex_unlock(&spic_dev.lock);

	return count;
}

static ssize_t sony_pic_wwanpower_show(struct device *dev, struct device_attribute *attr, char *buffer){
	ssize_t count;
	mutex_lock(&spic_dev.lock);
	count = sysfs_emit( buffer, "%d\n", spic_dev.wwan_power );
	mutex_unlock(&spic_dev.lock);
	return count;
}

/************************************
*  bluetooth subsystem power state  *
************************************/
static void __sony_pic_set_bluetoothpower(u8 state){
	state = !!state;
	if (spic_dev.bluetooth_power == state) return;
	sony_pic_call2(0x96, state);
	sony_pic_call1(0x82);
	spic_dev.bluetooth_power = state;
}

static ssize_t sony_pic_bluetoothpower_store( struct device *dev, struct device_attribute *attr, const char *buffer, size_t count ){
	unsigned long value;
	if (count > 31) return -EINVAL;
	if (kstrtoul(buffer, 10, &value)) return -EINVAL;
	mutex_lock(&spic_dev.lock);
	__sony_pic_set_bluetoothpower(value);
	mutex_unlock(&spic_dev.lock);
	return count;
}

static ssize_t sony_pic_bluetoothpower_show(struct device *dev, struct device_attribute *attr, char *buffer){
	ssize_t count = 0;
	mutex_lock(&spic_dev.lock);
	count = sysfs_emit( buffer, "%d\n", spic_dev.bluetooth_power );
	mutex_unlock(&spic_dev.lock);
	return count;
}

/***********************************************************
*  FAN0 information (reverse engineered from ACPI tables)  *
***********************************************************/
#define SONY_PIC_FAN0_STATUS	0x93
static int sony_pic_set_fanspeed(unsigned long value){
	return ec_write(SONY_PIC_FAN0_STATUS, value);
}

static int sony_pic_get_fanspeed(u8 *value){
	return ec_read(SONY_PIC_FAN0_STATUS, value);
}

static ssize_t sony_pic_fanspeed_store( struct device *dev, struct device_attribute *attr, const char *buffer, size_t count ){
	unsigned long value;
	if (count > 31)return -EINVAL;
	if (kstrtoul(buffer, 10, &value))return -EINVAL;
	if (sony_pic_set_fanspeed(value))return -EIO;
	return count;
}

static ssize_t sony_pic_fanspeed_show( struct device *dev, struct device_attribute *attr, char *buffer ){
	u8 value = 0;
	if (sony_pic_get_fanspeed(&value)) return -EIO;
	return sysfs_emit( buffer, "%d\n", value );
}

/************
*   ATTRS   *
************/

#define SPIC_ATTR(_name, _mode)					\
struct device_attribute spic_attr_##_name = __ATTR(_name,	\
		_mode, sony_pic_## _name ##_show,		\
		sony_pic_## _name ##_store)

static SPIC_ATTR(bluetoothpower, 0644);
static SPIC_ATTR(wwanpower, 0644);
static SPIC_ATTR(fanspeed, 0644);

static struct attribute *spic_attributes[] = {
	&spic_attr_bluetoothpower.attr,
	&spic_attr_wwanpower.attr,
	&spic_attr_fanspeed.attr,
	NULL
};


static const struct attribute_group spic_attribute_group = {
	.attrs = spic_attributes
};

/***********************************
*   ISR: some event is available   *
***********************************/

irqreturn_t sony_pic_irq_handler( int irq, void *dev_id ){
	int i, j;
	u8 ev = 0;
	u8 data_mask = 0;
	
	struct sony_pic_dev *dev = (struct sony_pic_dev *) dev_id;
	
	ev = inb_p( dev->cur_ioport->io1.minimum );
	if( dev->cur_ioport->io2.minimum ) data_mask = inb_p( dev->cur_ioport->io2.minimum );
	else data_mask = inb_p( dev->cur_ioport->io1.minimum + dev->evport_offset );
	
	
	/*
	printk( KERN_NOTICE "[%s] event [%.2x] mask [%.2x] port 0x%.4x(+0x%.2x)\n",
		KBUILD_MODNAME,
		ev,
		data_mask,
		dev->cur_ioport->io1.minimum,
		dev->evport_offset
	);
	*/
	
	
	if (ev == 0x00 || ev == 0xff) return IRQ_HANDLED;
	
	
	for (i = 0; dev->event_types[i].data; i++) {
		if( data_mask != dev->event_types[i].data ) continue;
		for( j = 0; dev->event_types[i].events[j].data; j++ ) {
			if( ev == dev->event_types[i].events[j].data ) {
				vgnux_input_sendkey( dev->event_types[i].events[j].key );
				
				//events were thrown out as unnecessary
				//sonypi_compat_report_event( dev->event_types[i].events[j].event );
				
				return IRQ_HANDLED;
			}
		}
	}
	
	
	//Still not able to decode the event try to pass it over to the minidriver
	if( dev->handle_irq && dev->handle_irq(data_mask, ev) == 0 ) return IRQ_HANDLED;
	
	printk( KERN_WARNING "[%s] unknown event ([%.2x] [%.2x]) at port 0x%.4x(+0x%.2x)\n",
		KBUILD_MODNAME,
		ev,
		data_mask,
		dev->cur_ioport->io1.minimum,
		dev->evport_offset
	);
	return IRQ_HANDLED;
}

static int type3_handle_irq( const u8 data_mask, const u8 ev ){
	/*
	 * 0x31 could mean we have to take some extra action and wait for
	 * the next irq for some Type3 models, it will generate a new
	 * irq and we can read new data from the device:
	 *  - 0x5c and 0x5f requires 0xA0
	 *  - 0x61 requires 0xB3
	 */
	if( data_mask == 0x31 ){
		if ( ev == 0x5c || ev == 0x5f ) sony_pic_call1( 0xA0 );
		else if( ev == 0x61 ) sony_pic_call1( 0xB3 );
		return 0;
	}
	return 1;
}

/***********************
*    ACPI RESOURCES    *
***********************/
static acpi_status sony_pic_read_possible_resource( struct acpi_resource *resource, void *context ){
	u32 i;
	struct sony_pic_dev *dev = (struct sony_pic_dev *)context;

	switch (resource->type) {
	case ACPI_RESOURCE_TYPE_START_DEPENDENT:
		{
			/* start IO enumeration */
			struct sony_pic_ioport *ioport = kzalloc(sizeof(*ioport), GFP_KERNEL);
			if (!ioport)
				return AE_ERROR;

			list_add(&ioport->list, &dev->ioports);
			return AE_OK;
		}

	case ACPI_RESOURCE_TYPE_END_DEPENDENT:
		/* end IO enumeration */
		return AE_OK;

	case ACPI_RESOURCE_TYPE_IRQ:
		{
			struct acpi_resource_irq *p = &resource->data.irq;
			struct sony_pic_irq *interrupt = NULL;
			if (!p || !p->interrupt_count) {
				/*
				 * IRQ descriptors may have no IRQ# bits set,
				 * particularly those those w/ _STA disabled
				 */
				printk( KERN_NOTICE "[%s] Blank IRQ resource\n", KBUILD_MODNAME );
				return AE_OK;
			}
			for (i = 0; i < p->interrupt_count; i++) {
				if (!p->interrupts[i]) {
					pr_warn( "Invalid IRQ %d\n", p->interrupts[i] );
					continue;
				}
				interrupt = kzalloc(sizeof(*interrupt), GFP_KERNEL );
				if( !interrupt ) return AE_ERROR;

				list_add(&interrupt->list, &dev->interrupts);
				interrupt->irq.triggering = p->triggering;
				interrupt->irq.polarity = p->polarity;
				interrupt->irq.shareable = p->shareable;
				interrupt->irq.interrupt_count = 1;
				interrupt->irq.interrupts[0] = p->interrupts[i];
			}
			return AE_OK;
		}
	case ACPI_RESOURCE_TYPE_IO:
		{
			struct acpi_resource_io *io = &resource->data.io;
			struct sony_pic_ioport *ioport =
				list_first_entry(&dev->ioports, struct sony_pic_ioport, list);
			if (!io) {
				printk( KERN_NOTICE "[%s] Blank IO resource\n", KBUILD_MODNAME );
				return AE_OK;
			}

			if (!ioport->io1.minimum) {
				memcpy(&ioport->io1, io, sizeof(*io));
				printk( KERN_NOTICE "[%s] IO1 at 0x%.4x (0x%.2x)\n", KBUILD_MODNAME, ioport->io1.minimum, ioport->io1.address_length );
			}
			else if (!ioport->io2.minimum) {
				memcpy(&ioport->io2, io, sizeof(*io));
				printk( KERN_NOTICE "[%s] IO2 at 0x%.4x (0x%.2x)\n", KBUILD_MODNAME, ioport->io2.minimum, ioport->io2.address_length );
			}
			else {
				pr_err( "Unknown SPIC Type, more than 2 IO Ports\n" );
				return AE_ERROR;
			}
			return AE_OK;
		}

	case ACPI_RESOURCE_TYPE_END_TAG:
		return AE_OK;

	default:
		printk( KERN_NOTICE "[%s] Resource %d isn't an IRQ nor an IO port\n", KBUILD_MODNAME, resource->type );
		return AE_CTRL_TERMINATE;

	}
}

static int sony_pic_possible_resources( struct acpi_device *device ){
	int result = 0;
	acpi_status status = AE_OK;

	if (!device) return -EINVAL;

	/* get device status */
	/* see acpi_pci_link_get_current acpi_pci_link_get_possible */
	printk( KERN_NOTICE "[%s] Evaluating _STA\n", KBUILD_MODNAME );
	result = acpi_bus_get_status(device);
	if( result ){
		pr_warn("Unable to read status\n");
		goto end;
	}

	if( !device->status.enabled ) printk( KERN_NOTICE "[%s] Device disabled\n", KBUILD_MODNAME );
	else printk( KERN_NOTICE "[%s] Device enabled\n", KBUILD_MODNAME );

	//Query and parse 'method'
	printk( KERN_NOTICE "[%s] Evaluating %s\n", KBUILD_MODNAME, METHOD_NAME__PRS );
	status = acpi_walk_resources( device->handle, METHOD_NAME__PRS, sony_pic_read_possible_resource, &spic_dev );
	if( ACPI_FAILURE(status) ){
		pr_warn( "Failure evaluating %s\n", METHOD_NAME__PRS );
		result = -ENODEV;
	}
end:
	return result;
}

/***************************
*    DETECT DEVICE TYPE    *
***************************/

static void sony_pic_detect_device_type( struct sony_pic_dev *dev ){
	
	struct pci_dev *pcidev;
	
	pcidev = pci_get_device( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3, NULL );
	if( pcidev ){
		dev->model = SONYPI_DEVICE_TYPE1;
		dev->evport_offset = SONYPI_TYPE1_OFFSET;
		dev->event_types = dummy_events;
		goto out;
	}
	
	pcidev = pci_get_device( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, NULL );
	if( pcidev ){
		dev->model = SONYPI_DEVICE_TYPE2;
		dev->evport_offset = SONYPI_TYPE2_OFFSET;
		dev->event_types = dummy_events;
		goto out;
	}
	
	pcidev = pci_get_device( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, NULL );//VGN-UX
	if( pcidev ){
		dev->model = SONYPI_DEVICE_TYPE3;
		dev->handle_irq = type3_handle_irq;
		dev->evport_offset = SONYPI_TYPE3_OFFSET;
		//dev->event_types = type3_events;
		dev->event_types = vgnux_events;
		goto out;
	}

	pcidev = pci_get_device(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH8_4, NULL );
	if( pcidev ){
		dev->model = SONYPI_DEVICE_TYPE3;
		dev->handle_irq = type3_handle_irq;
		dev->evport_offset = SONYPI_TYPE3_OFFSET;
		dev->event_types = dummy_events;
		goto out;
	}
	
	pcidev = pci_get_device( PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH9_1, NULL );
	if( pcidev ){
		dev->model = SONYPI_DEVICE_TYPE3;
		dev->handle_irq = type3_handle_irq;
		dev->evport_offset = SONYPI_TYPE3_OFFSET;
		dev->event_types = dummy_events;
		goto out;
	}
	
	/* default */
	dev->model = SONYPI_DEVICE_TYPE2;
	dev->evport_offset = SONYPI_TYPE2_OFFSET;
	dev->event_types = dummy_events;
	
out:
	pci_dev_put(pcidev);
	
	pr_info("[%s]detected Type%d model\n",
		KBUILD_MODNAME,
		dev->model == SONYPI_DEVICE_TYPE1 ? 1 :
		dev->model == SONYPI_DEVICE_TYPE2 ? 2 : 3 );
}

/*************************
*    SPIC DEVICE INIT    *
*************************/
//Based on drivers/acpi/pci_link.c:acpi_pci_link_set
//Call _SRS to set current resources

static int sony_pic_enable( struct acpi_device *device, struct sony_pic_ioport *ioport, struct sony_pic_irq *irq ){
	acpi_status status;
	int result = 0;
	/* Type 1 resource layout is:
	 *    IO
	 *    IO
	 *    IRQNoFlags
	 *    End
	 *
	 * Type 2 and 3 resource layout is:
	 *    IO
	 *    IRQNoFlags
	 *    End
	 */
	struct {
		struct acpi_resource res1;
		struct acpi_resource res2;
		struct acpi_resource res3;
		struct acpi_resource res4;
	} *resource;
	struct acpi_buffer buffer = { 0, NULL };

	if (!ioport || !irq) return -EINVAL;

	/* init acpi_buffer */
	resource = kzalloc(sizeof(*resource) + 1, GFP_KERNEL);
	if (!resource) return -ENOMEM;

	buffer.length = sizeof(*resource) + 1;
	buffer.pointer = resource;

	/* setup Type 1 resources */
	if (spic_dev.model == SONYPI_DEVICE_TYPE1) {

		/* setup io resources */
		resource->res1.type = ACPI_RESOURCE_TYPE_IO;
		resource->res1.length = sizeof(struct acpi_resource);
		memcpy(&resource->res1.data.io, &ioport->io1, sizeof(struct acpi_resource_io));

		resource->res2.type = ACPI_RESOURCE_TYPE_IO;
		resource->res2.length = sizeof(struct acpi_resource);
		memcpy(&resource->res2.data.io, &ioport->io2, sizeof(struct acpi_resource_io));

		/* setup irq resource */
		resource->res3.type = ACPI_RESOURCE_TYPE_IRQ;
		resource->res3.length = sizeof(struct acpi_resource);
		memcpy(&resource->res3.data.irq, &irq->irq, sizeof(struct acpi_resource_irq));
		/* we requested a shared irq */
		resource->res3.data.irq.shareable = ACPI_SHARED;

		resource->res4.type = ACPI_RESOURCE_TYPE_END_TAG;
		resource->res4.length = sizeof(struct acpi_resource);
	}
	/* setup Type 2/3 resources */
	else {
		/* setup io resource */
		resource->res1.type = ACPI_RESOURCE_TYPE_IO;
		resource->res1.length = sizeof(struct acpi_resource);
		memcpy(&resource->res1.data.io, &ioport->io1,
				sizeof(struct acpi_resource_io));

		/* setup irq resource */
		resource->res2.type = ACPI_RESOURCE_TYPE_IRQ;
		resource->res2.length = sizeof(struct acpi_resource);
		memcpy(&resource->res2.data.irq, &irq->irq,	sizeof(struct acpi_resource_irq));
		/* we requested a shared irq */
		resource->res2.data.irq.shareable = ACPI_SHARED;

		resource->res3.type = ACPI_RESOURCE_TYPE_END_TAG;
		resource->res3.length = sizeof(struct acpi_resource);
	}

	/* Attempt to set the resource */
	printk( KERN_NOTICE "[%s] Evaluating _SRS\n", KBUILD_MODNAME );
	status = acpi_set_current_resources(device->handle, &buffer);

	/* check for total failure */
	if (ACPI_FAILURE(status)) {
		pr_err("Error evaluating _SRS\n");
		result = -ENODEV;
		goto end;
	}

	/* Necessary device initializations calls (from sonypi) */
	sony_pic_call1(0x82);
	sony_pic_call2(0x81, 0xff);
	sony_pic_call1(compat ? 0x92 : 0x82);

end:
	kfree(resource);
	return result;
}

//Disable the spic device by calling its _DIS method
static int sony_pic_disable( struct acpi_device *device ){
	acpi_status ret = acpi_evaluate_object( device->handle, "_DIS", NULL, NULL );
	if( ACPI_FAILURE(ret) && ret != AE_NOT_FOUND ) return -ENXIO;
	printk( KERN_NOTICE "[%s] Device disabled\n", KBUILD_MODNAME );
	return 0;
}

static int sony_pic_add( struct acpi_device* device ) {
	int result;
	struct sony_pic_ioport *io, *tmp_io;
	struct sony_pic_irq *irq, *tmp_irq;
	
	printk( KERN_NOTICE "[%s] SPIC setup...\n", KBUILD_MODNAME );
	
	spic_dev.acpi_dev = device;
	strcpy(acpi_device_class(device), "sony/hotkey");
	sony_pic_detect_device_type(&spic_dev);
	mutex_init(&spic_dev.lock);

	/* read _PRS resources */
	result = sony_pic_possible_resources( device );
	if( result ){
		pr_err( "[%s]Unable to read possible resources\n", KBUILD_MODNAME );
		goto err_free_resources;
	}

	//setup input device
	result = vgnux_register_input();
	if( result ){
		pr_err("[%s]Unable to create input device\n", KBUILD_MODNAME );
		goto err_free_resources;
	}

	result = sonypi_compat_init();
	if( result ) goto err_remove_input;

	/* request io port */
	list_for_each_entry_reverse(io, &spic_dev.ioports, list) {
		if (request_region(io->io1.minimum, io->io1.address_length,
					"Sony Programmable I/O Device")) {
			printk( KERN_NOTICE "[%s] I/O port1: 0x%.4x (0x%.4x) + 0x%.2x\n", 
				KBUILD_MODNAME,
				io->io1.minimum,
				io->io1.maximum,
				io->io1.address_length
			);
			/* Type 1 have 2 ioports */
			if (io->io2.minimum) {
				if (request_region(io->io2.minimum,
						io->io2.address_length,
						"Sony Programmable I/O Device")) {
					printk( KERN_NOTICE "[%s] I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n",
						KBUILD_MODNAME,
						io->io2.minimum,
						io->io2.maximum,
						io->io2.address_length
					);
					spic_dev.cur_ioport = io;
					break;
				}
				else {
					printk( KERN_ERR "[%s] Unable to get I/O port2: 0x%.4x (0x%.4x) + 0x%.2x\n",
						KBUILD_MODNAME,
						io->io2.minimum,
						io->io2.maximum,
						io->io2.address_length
					);
					release_region( io->io1.minimum, io->io1.address_length );
				}
			}
			else {
				spic_dev.cur_ioport = io;
				break;
			}
		}
	}
	if (!spic_dev.cur_ioport) {
		pr_err( "[%s]Failed to request_region\n", KBUILD_MODNAME );
		result = -ENODEV;
		goto err_remove_compat;
	}

	/* request IRQ */
	list_for_each_entry_reverse(irq, &spic_dev.interrupts, list) {
		if( !request_irq(irq->irq.interrupts[0], sony_pic_irq_handler, 0, KBUILD_MODNAME, &spic_dev) ){
			printk( KERN_NOTICE "[%s] IRQ: %d - triggering: %d - polarity: %d - shr: %d\n",
				KBUILD_MODNAME,
				irq->irq.interrupts[0],
				irq->irq.triggering,
				irq->irq.polarity,
				irq->irq.shareable
			);
			spic_dev.cur_irq = irq;
			break;
		}
	}
	if (!spic_dev.cur_irq) {
		pr_err( "[%s]Failed to request_irq\n", KBUILD_MODNAME );
		result = -ENODEV;
		goto err_release_region;
	}

	/* set resource status _SRS */
	result = sony_pic_enable(device, spic_dev.cur_ioport, spic_dev.cur_irq);
	if ( result ) {
		printk( KERN_ERR "[%s] Couldn't enable device\n", KBUILD_MODNAME );
		goto err_free_irq;
	}

	spic_dev.bluetooth_power = -1;
	/* create device attributes */
	result = sony_pf_add();
	if (result) goto err_disable_device;

	result = sysfs_create_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
	if (result) goto err_remove_pf;

	pr_info( "[%s] SPIC setup done.\n", KBUILD_MODNAME );
	return 0;

err_remove_pf:
	sony_pf_remove();

err_disable_device:
	sony_pic_disable(device);

err_free_irq:
	free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);

err_release_region:
	release_region(spic_dev.cur_ioport->io1.minimum, spic_dev.cur_ioport->io1.address_length);
	if (spic_dev.cur_ioport->io2.minimum)
		release_region(spic_dev.cur_ioport->io2.minimum, spic_dev.cur_ioport->io2.address_length);

err_remove_compat:
	sonypi_compat_exit();

err_remove_input:
	vgnux_unregister_input();

err_free_resources:
	list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
		list_del(&io->list);
		kfree(io);
	}
	list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
		list_del(&irq->list);
		kfree(irq);
	}
	spic_dev.cur_ioport = NULL;
	spic_dev.cur_irq = NULL;

	return result;
}

static int sony_pic_remove( struct acpi_device* device ){
	struct sony_pic_ioport *io, *tmp_io;
	struct sony_pic_irq *irq, *tmp_irq;

	if( sony_pic_disable(device) ) {
		pr_err("Couldn't disable device\n");
		return -ENXIO;
	}

	free_irq(spic_dev.cur_irq->irq.interrupts[0], &spic_dev);
	release_region(spic_dev.cur_ioport->io1.minimum, spic_dev.cur_ioport->io1.address_length);
	if (spic_dev.cur_ioport->io2.minimum)
		release_region(spic_dev.cur_ioport->io2.minimum, spic_dev.cur_ioport->io2.address_length);

	sonypi_compat_exit();
	vgnux_unregister_input();

	/* pf attrs */
	sysfs_remove_group(&sony_pf_device->dev.kobj, &spic_attribute_group);
	sony_pf_remove();

	list_for_each_entry_safe(io, tmp_io, &spic_dev.ioports, list) {
		list_del(&io->list);
		kfree(io);
	}
	list_for_each_entry_safe(irq, tmp_irq, &spic_dev.interrupts, list) {
		list_del(&irq->list);
		kfree(irq);
	}
	spic_dev.cur_ioport = NULL;
	spic_dev.cur_irq = NULL;

	printk( KERN_NOTICE "[%s] "SONY_PIC_DRIVER_NAME " removed.\n", KBUILD_MODNAME );
	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int sony_pic_suspend( struct device* dev ){
	if( sony_pic_disable(to_acpi_device(dev)) ) return -ENXIO;
	return 0;
}

static int sony_pic_resume( struct device* dev ){
	sony_pic_enable( to_acpi_device(dev), spic_dev.cur_ioport, spic_dev.cur_irq );
	return 0;
}
#endif



/********************
*    MODULE INIT    *
********************/

static int __init onLoad( void ){
	
	int result = 0;
	
	printk( KERN_NOTICE "%s is here.\n", KBUILD_MODNAME );
	
	if( !dmi_check_system( sonypi_dmi_table ) ){
		printk( KERN_NOTICE "[%s] Compatable hardware not found.\n", KBUILD_MODNAME );
		return -ENODEV;
	}
	
	result = acpi_bus_register_driver( &sony_pic_driver );
	if( result ) printk( KERN_ERR "[%s] Unable to register SPIC driver\n", KBUILD_MODNAME );
	return result;
}

static void __exit onUnload( void ){
	acpi_bus_unregister_driver( &sony_pic_driver );
	printk( KERN_NOTICE "%s has been unloaded.\n", KBUILD_MODNAME );
}

module_init( onLoad );
module_exit( onUnload );
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE( acpi, sony_pic_device_ids );
